/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package canvaswork;

import geomerative.RG;
import geomerative.RPoint;
import geomerative.RShape;
import processing.core.PApplet;
import processing.core.PFont;

import javax.swing.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

/**
 * @author jonatanhilden
 */
public class Main extends PApplet {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        PApplet.main(new String[]{"canvaswork.Main"});
    }

    public static final int SNAP_DISTANCE = 10;
    private ArrayList properties; // this array stores the properties read in from an external file
    private ArrayList<Grob> items;
    private Set<Grob> selectedItems;
    private Set<Grob> nonSelectedItems;
    private Set<Grob> temporarySelection;
    private ArrayList shelf; // the shelf items
    private Menu menu;
    Marquee marquee; // the marquee selection tool
    PFont unibody;
    TextBox textbox;
    Button snapButton;
    int itemCount;// itemcount is solely used for determining the length of the input list of grobs
    int gridSize = 1;
    int canvasWidth = 800;
    int canvasHeight = 600;
    int defaultColor = unhex("FF5DFF81");
    int MAXVALUE = Integer.MAX_VALUE;
    int min_x, min_y;
    // snapping values for grobs
    int cornerPointDraggingGrobX = 0;
    int cornerPointDraggingGrobY = 0;
    int cornerPointSnappingGrobX = 0;
    int cornerPointSnappingGrobY = 0;
    int virtualSnapPointX = 0;
    int virtualSnapPointY = 0;
    int snapPointX = 0;
    int snapPointY = 0;
    int snapRotation = 0;
    Selectable active;
    Selectable activeItemItems;
    Selectable activeItemShelf;
    Grob dragGrob;
    int dragGrobHandleNr;
    int dragGrobSelectedSideNr = -1;
    Grob snaptargetGrob;
    Grob snapSidetargetGrob; // when snapping temporarily to a side.
    RPoint rotationCenter;
    int closestGrobSideNr;
    private int previousX;
    private int previousY;
    float distanceGrobToGrob;
    float distanceHandleToSide;
    private boolean altPressed;
    boolean shiftPressed = false;
    boolean snapIsOn = false;
    private String[] lines;
    private boolean rotateContinuosly = false;

    // Basic update  method for Items
    private void updateItems() {
        // updating the shelf grobs
//        overGrob = setOverGrob(shelf);
//        overGrob = setOverGrob(items);
        Selectable aI = null;
        for (int i = 0; i < items.size(); i++) {
            Grob grob = (Grob) items.get(i);
            grob.update();
            grob.draw();
            if (grob.getShape().contains(mouseX, mouseY)) {
                // setting active item to the hovered Grob
                aI = ActiveItem(grob);
            }
            min_x = selectionBounds(canvasWidth, 0);
            min_y = selectionBounds(canvasHeight, 1);
            if (rotateContinuosly) {
                rotateSelected(45);
            }
            if (marquee.hasShp()) {
                // marquee selection with geomerative intersect
                if (grob.contains(marquee.getShp())) {
                    selectTemporary(grob);
                } else {
                    deselectTemporary(grob);
                }
            }
        }
        activeItemItems = aI;

    }

    // selection and ArrayList modification methods
    void singleSelect(Grob grob) {
        selectedItems.clear();
        selectedItems.add(grob);
    }

    void select(Grob grob) {
        selectedItems.add(grob);
    }

    void deselect(Grob grob) {
        selectedItems.remove(grob);
    }

    void deselectAll() {
        selectedItems.clear();
    }

    boolean isSelected(Grob grob) {
        if (selectedItems.contains(grob)) {// recursive check. Goes through the tree of grobs/parents. Useful design pattern!
            // println(items.indexOf(grob));
            return true;
        } else if (grob.hasParent()) {
            return isSelected(grob.getParent());
        }
        return false;
    }

    /*
    while (grob != null) {//iterative version. grob= the last grob, exits when getParent=null
    if (selectedItems.contains(grob)) {
    return true;
    }
    grob = grob.getParent();
    }
    return false;*/
    int selectionSize() {
        return selectedItems.size();
    }

    int selectionBounds(int bounds, int mode) {
        int xx = bounds;
        int yy = bounds;
        // if the smallest relativePosition value is inside the canvas, that value is used.
        for (int i = 0; i < items.size(); i++) {
            Grob grob = (Grob) items.get(i);
            if (grob.isSelected()) {
                if (mode == 0) {
                    if (grob.getXpos() < xx) {
                        xx = (int) grob.getXpos();
                    }
                } else if (grob.getYpos() < yy) {
                    yy = (int) grob.getYpos();
                }
            }
        }

        if (mode == 0) {// deciding whether to return x- or y-values
            return xx;
        } else {
            return yy;
        }
    }

    void selectTemporary(Grob grob) {
        temporarySelection.add(grob);
    }

    void deselectAllTemporary() {
        temporarySelection.clear();
    }

    void deselectTemporary(Grob grob) {
        temporarySelection.remove(grob);
    }

    void deleteGrobs() {
        for (Grob grob : selectedItems) {
            items.remove(grob);
        }
    }

    boolean isTemporarySelected(Grob grob) {
        if (temporarySelection.contains(grob)) {// recursive check. Goes through the tree of grobs/parents.
            return true;
        } else if (grob.hasParent()) {
            return isTemporarySelected(grob.getParent());
        }
        return false;
    }

    Selectable ActiveItem(Selectable grob) {
        if (grob.getShape().contains(mouseX, mouseY)) {
            return grob;
        } else {
            return null;
        }
    }

    boolean isOver(Selectable grob) {
        return grob == active;

//        return grob.getShape().contains(mouseX, mouseY);


    }

    // Group utility functions
    void createGroup() {
        int groupBound_x, groupBound_y;
        groupBound_x =
                selectionBounds(canvasWidth, 0);
        groupBound_y =
                selectionBounds(canvasHeight, 1);
        Group group = new Group(this, null, 0, groupBound_x, groupBound_y, 0, 0, 0);
        for (Grob grob : selectedItems) {
            if (!group.hasChild(grob)) {
                group.addChild(grob);
                items.remove(grob);
            }

        }
        group.initiateShape();
        println("group " + group + " " + group.childCount());
        items.add(group);
        selectedItems.clear();
        selectedItems.add(group);
        println("items " + items);


    }

    void unGroup(Group group) {
        for (Grob grob : group.getChildren()) {
            grob.setParent(null);


            int rx = Math.round(grob.getRelativePosition().x);
            int ry = Math.round(grob.getRelativePosition().y);
            int gCx=      Math.round(group.getCenter().x);
            int gCy=   Math.round(group.getCenter().y);


            // translation method #1 Relative positioning unreliable for grobs of different sizes.
//
//            grob.setRelativePosition(grob.getXpos(), grob.getYpos());
//            grob.translateRelativePosition(-Math.round(group.getCenter().x),- Math.round(group.getCenter().y));
//            grob.getRelativePosition().rotate((PApplet.radians(group.rotation)));
//            grob.translateRelativePosition(Math.round(group.getCenter().x), Math.round(group.getCenter().y));

//            grob.setRelativePosition(grob.getXpos(), grob.getYpos());
//            grob.translateRelativePosition(-group.getXpos(),-group.getYpos());
//            grob.getRelativePosition().rotate((PApplet.radians(group.rotation)));
//            grob.translateRelativePosition(group.getXpos(), group.getYpos());

            grob.setRelativePosition(0, 0);
            grob.translateRelativePosition(gCx, gCy);
            grob.getRelativePosition().rotate((PApplet.radians(group.rotation)));
            grob.translateRelativePosition(grob.getXpos()-gCx, grob.getYpos()-gCy);
            grob.translateRelativePosition(group.getXpos()-grob.getXpos(), group.getYpos()-grob.getYpos());
            grob.translateRelativePosition(rx, ry);

            
            // translation method  #2 large offsets for bigger groups, relative positioning correct
//            grob.getRelativePosition().rotate((PApplet.radians(group.rotation)));
//            grob.translateRelativePosition(group.getXpos(), group.getYpos());

//            grob.translateRelativePosition(Math.round(group.getCenter().x) - group.getXpos(), Math.round(group.getCenter().y) - group.getYpos());
//            grob.translateRelativePosition(Math.round(group.getCenter().x) - group.getXpos(), Math.round(group.getCenter().y) - group.getYpos());


            Main.println("Child  xpos " + grob.xpos + "ypos " + grob.ypos);
            Main.println("Child  position x " + grob.getRelativePosition().x + "y " + grob.getRelativePosition().y);


            grob.setRotation(group.getRotation() + grob.getRotation());
            //updating the relativePosition on ungroup
            grob.setXpos((int) grob.getRelativePosition().x);
            grob.setYpos((int) grob.getRelativePosition().y);
            items.add(grob);
            selectedItems.add(grob);
            group.removeChild(grob);
        }

        items.remove(group);
        selectedItems.remove(group);

    }

// geometry utility methods

    //function for calculating angle of two lines
    float angle(float x0, float y0, float x1, float y1) {
        float a = (atan2(y1 - y0, x1 - x0));
        return a;
    }

    float angleOfSide(RShape side) { // flips the points to always return a positive angle…?
        float x0 = side.getHandles()[0].x;
        float y0 = side.getHandles()[0].y;
        float x1 = side.getHandles()[1].x;
        float y1 = side.getHandles()[1].y;
//        if (x0 < x1) {
//            float tempX = x0;
//            x0 =
//                    x1;
//            x1 =
//                    tempX;
//        }
//
//        if (y0 < y1) {
//            float tempY = y0;
//            y0 =
//                    y1;
//            y1 =
//                    tempY;
//        }

        return angle(x0, y0, x1, y1);
    }

    int calculateRotationSides(RShape side1, RShape side2) {
        int r = 0;
        float a1 = angleOfSide(side1);
        float a2 = angleOfSide(side2);
        println("angle 1 " + a1 + " angle2 " + a2);

        if (Math.abs(a1) == Math.abs(a2)) {
            return r;
        }
        if (a1 == PI && a2 == 0 || a1 == 0 && a2 == PI) {
            return r;
        }

        if (Math.round(degrees(a1 - a2)) == 0) {
            r = 0;
        } else {
            r = Math.round(degrees(PI + a1 - a2));
        }
        return r;
    }

    float distanceToPoint(int x0, int y0, int x1, int y1) {
        return sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2));
    }

    float distanceToPoint(float x0, float y0, float x1, float y1) {
        return sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2));
    }

    float distanceToPoint(RPoint point0, RPoint point1) {// version with RPoints
        float x0 = point0.x;
        float y0 = point0.y;
        float x1 = point1.x;
        float y1 = point1.y;
        return sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2));
    }

    float distanceToLine(int x0, int y0, int x1, int y1, int x2, int y2) {
        float d;
        // distance to an infinite line through 1, 2
        d =
                abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1)) /
                        sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));

        // controlling for a finite line

        if (x0 < x1 || y0 < y1 || y0 > y1 && (d < distanceToPoint(x0, y0, x1, y1))) {
            d = distanceToPoint(x0, y0, x1, y1);
        } else if (x0 > x2 || y0 > y2 && (d < distanceToPoint(x0, y0, x2, y2))) {
            d = distanceToPoint(x0, y0, x2, y2);
        }

        return d;

    }

    float distanceToInfiniteLine(int x0, int y0, int x1, int y1, int x2, int y2) {
        float d;
        // distance to an infinite line through 1, 2
        d =
                abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1)) /
                        sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
        return d;

    }

    float[] dist_ClosestPointOnSegment(int cx, int cy, RShape line) {
        //version that takes a RPoint and a RShape with two points and returns [0] distance and [1]x, [2].y closest point  on the line
        // math by Philip Nicoletti,  June 14th, 2002 05:18 PM,
        // http://www.codeguru.com/forum/printthread.php?t=194400
        float[] distAndPoint = new float[3];

        if (line == null) {
            distAndPoint[0] = Float.MAX_VALUE;
            distAndPoint[1] = 0;
            distAndPoint[2] = 0;

            return distAndPoint;
        }


        float ax = line.getHandles()[0].x;
        float ay = line.getHandles()[0].y;
        float bx = line.getHandles()[1].x;
        float by = line.getHandles()[1].y;

        //
        // find the distance from the point (cx,cy) to the line
        // determined by the points (ax,ay) and (bx,by)
        //
        // distanceSegment = distance from the point to the line segment
        // distanceLine = distance from the point to the line (assuming
        //                                        infinite extent in both directions
        //

        /*

        Subject 1.02: How do I find the distance from a point to a line?


        Let the point be C (Cx,Cy) and the line be AB (Ax,Ay) to (Bx,By).
        Let P be the point of perpendicular projection of C on AB.  The parameter
        r, which indicates P's relativePosition along AB, is computed by the dot product
        of AC and AB divided by the square of the length of AB:

        (1)    AC dot AB
        r = ---------
        ||AB||^2

        r has the following meaning:

        r=0      P = A
        r=1      P = B
        r<0      P is on the backward extension of AB
        r>1      P is on the forward extension of AB
        0<r<1    P is interior to AB

        The length of a line segment in d dimensions, AB is computed by:

        L = sqrt( (Bx-Ax)^2 + (By-Ay)^2 + ... + (Bd-Ad)^2)

        so in 2D:

        L = sqrt( (Bx-Ax)^2 + (By-Ay)^2 )

        and the dot product of two vectors in d dimensions, U dot V is computed:

        D = (Ux * Vx) + (Uy * Vy) + ... + (Ud * Vd)

        so in 2D:

        D = (Ux * Vx) + (Uy * Vy)

        So (1) expands to:

        (Cx-Ax)(Bx-Ax) + (Cy-Ay)(By-Ay)
        r = -------------------------------
        L^2

        The point P can then be found:

        Px = Ax + r(Bx-Ax)
        Py = Ay + r(By-Ay)

        And the distance from A to P = r*L.

        Use another parameter s to indicate the location along PC, with the
        following meaning:
        s<0      C is left of AB
        s>0      C is right of AB
        s=0      C is on AB

        Compute s as follows:

        (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay)
        s = -----------------------------
        L^2


        Then the distance from C to P = |s|*L.

         */
        float distanceSegment;


        float r_numerator = (cx - ax) * (bx - ax) + (cy - ay) * (by - ay);
        float r_denomenator = (bx - ax) * (bx - ax) + (by - ay) * (by - ay);
        float r = r_numerator / r_denomenator;
//
        float px = ax + r * (bx - ax);
        float py = ay + r * (by - ay);
//
        float s = ((ay - cy) * (bx - ax) - (ax - cx) * (by - ay)) / r_denomenator;

        float distanceLine = Math.abs((s) * sqrt(r_denomenator));

//
// (xx,yy) is the point on the lineSegment closest to (cx,cy)
//
        float xx = px;
        float yy = py;

        if ((r >= 0) && (r <= 1)) {
            distanceSegment = distanceLine;
        } else {

            float dist1 = (cx - ax) * (cx - ax) + (cy - ay) * (cy - ay);
            float dist2 = (cx - bx) * (cx - bx) + (cy - by) * (cy - by);
            if (dist1 < dist2) {
                xx = ax;
                yy = ay;
                distanceSegment = sqrt(dist1);
            } else {
                xx = bx;
                yy = by;
                distanceSegment = sqrt(dist2);
            }


        }
        distAndPoint[0] = distanceSegment;
        distAndPoint[1] = xx;
        distAndPoint[2] = yy;

        return distAndPoint;
    }

    float[] dist_ClosestPointOnSegment(RPoint point, RShape line) {//version that takes a RPoint and a RShape with two points
        // code by Philip Nicoletti,  June 14th, 2002 05:18 PM,
        // http://www.codeguru.com/forum/printthread.php?t=194400

        //version that takes a RPoint and a RShape with two points and returns [0] distance and [1]x, [2].y closest point  on the line
        // math by Philip Nicoletti,  June 14th, 2002 05:18 PM,
        // http://www.codeguru.com/forum/printthread.php?t=194400
        float[] distAndPoint = new float[3];

        if (line == null) {
            distAndPoint[0] = Float.MAX_VALUE;
            distAndPoint[1] = 0;
            distAndPoint[2] = 0;

            return distAndPoint;
        }

        float cx = point.x;
        float cy = point.y;
        float ax = line.getHandles()[0].x;
        float ay = line.getHandles()[0].y;
        float bx = line.getHandles()[1].x;
        float by = line.getHandles()[1].y;


        float distanceSegment;


        float r_numerator = (cx - ax) * (bx - ax) + (cy - ay) * (by - ay);
        float r_denomenator = (bx - ax) * (bx - ax) + (by - ay) * (by - ay);
        float r = r_numerator / r_denomenator;
//
        float px = ax + r * (bx - ax);
        float py = ay + r * (by - ay);
//
        float s = ((ay - cy) * (bx - ax) - (ax - cx) * (by - ay)) / r_denomenator;

        float distanceLine = Math.abs((s) * sqrt(r_denomenator));

//
// (xx,yy) is the point on the lineSegment closest to (cx,cy)
//
        float xx = px;
        float yy = py;

        if ((r >= 0) && (r <= 1)) {
            distanceSegment = distanceLine;
        } else {

            float dist1 = (cx - ax) * (cx - ax) + (cy - ay) * (cy - ay);
            float dist2 = (cx - bx) * (cx - bx) + (cy - by) * (cy - by);
            if (dist1 < dist2) {
                xx = ax;
                yy = ay;
                distanceSegment = sqrt(dist1);
            } else {
                xx = bx;
                yy = by;
                distanceSegment = sqrt(dist2);
            }


        }
        distAndPoint[0] = distanceSegment;
        distAndPoint[1] = xx;
        distAndPoint[2] = yy;

        return distAndPoint;
    }

    RPoint closestPointOnSegment(RPoint point, RShape line) {
        // math by Philip Nicoletti,  June 14th, 2002 05:18 PM,
        // http://www.codeguru.com/forum/printthread.php?t=194400
        RPoint closestPoint;

        if (line == null) {
            closestPoint = new RPoint(0, 0);
            return closestPoint;
        }

        float cx = point.x;
        float cy = point.y;
        float ax = line.getHandles()[0].x;
        float ay = line.getHandles()[0].y;
        float bx = line.getHandles()[1].x;
        float by = line.getHandles()[1].y;


        float r_numerator = (cx - ax) * (bx - ax) + (cy - ay) * (by - ay);
        float r_denomenator = (bx - ax) * (bx - ax) + (by - ay) * (by - ay);
        float r = r_numerator / r_denomenator;
//
        float px = ax + r * (bx - ax);
        float py = ay + r * (by - ay);
//
//
// (xx,yy) is the point on the lineSegment closest to (cx,cy)
//
        float xx = px;
        float yy = py;

        if ((r >= 0) && (r <= 1)) {
        } else {

            float dist1 = (cx - ax) * (cx - ax) + (cy - ay) * (cy - ay);
            float dist2 = (cx - bx) * (cx - bx) + (cy - by) * (cy - by);
            if (dist1 < dist2) {
                xx = ax;
                yy = ay;
            } else {
                xx = bx;
                yy = by;
            }
        }
        closestPoint = new RPoint(xx, yy);
        return closestPoint;
    }

    int getSmallestValueIndex(float[] distances) {
        float min = max(distances);
        int closest = -1;
        for (int i = 0; i <
                distances.length; i++) {
            if (distances[i] <= min) {
                min = distances[i];
                closest =
                        i;
            }

        }
        return closest;
        //min value; closest grob;
    }

    // selecting grobs based on distance
    float distanceGrobToPoint(Grob grob, int x, int y) {
        if (!grob.isSelected()) {// looping through sides of unselected grobs to find closest point to x, y
            RShape[] sides = grob.getSides();
            float[] distances = new float[sides.length];
            int sideNum = -1;

            for (int i = 0; i <
                    distances.length; i++) {
                distances[i] = dist_ClosestPointOnSegment(x, y, sides[i])[0];
            }
//            grob.setSelectedSideNum(getSmallestValueIndex(distances)); // setting the selected side

            return min(distances);
        } else {
            return MAXVALUE; // return an impossible value for a selected grob
        }

    }

    // calculating distances if the side of draggingGrob is pre-seöected
    float distanceGrobToGrobSelectedSide(Grob grob, Grob grob2) {
        RShape grob2Side = grob2.getSide(dragGrobHandleNr);

        if (!grob.isSelected() && grob2Side != null) {
            RPoint handle0 = grob2Side.getHandles()[0];
            RPoint handle1 = grob2Side.getHandles()[1];
            // looping through sides of unselected grobs to find closest point to x, y
            RShape[] sides = grob.getSides();
            float[] distances = new float[sides.length];
            int sideNum = -1;

            for (int i = 0; i <
                    distances.length; i++) {
                float d0 = dist_ClosestPointOnSegment(handle0, sides[i])[0];
                float d1 = dist_ClosestPointOnSegment(handle1, sides[i])[0];
                if (d0 < d1) { // depending on closest handle, use that to set distance
                    distances[i] = d0;
                } else {
                    distances[i] = d1;
                }

            }
            return min(distances);
        } else {
            return MAXVALUE; // return an impossible value for a selected grob
        }

    }

    float distanceGrobToGrob(Grob grob, Grob grob2) {
        RPoint[] handles2 = grob2.getHandles();
        if (!grob.isSelected() && handles2 != null) {
            RShape[] sides = grob.getSides();
            float[] distances = new float[sides.length];
            float tempDistHandleSide = MAXVALUE;

            // looping through sides of grobs to find closest handle-side relation.

            for (int i = 0; i <
                    distances.length; i++) {
                for (int j = 0; j <
                        handles2.length; j++) {
                    //for each side, go through the handles on grob2 and find shortest distance.
                    float d = dist_ClosestPointOnSegment(handles2[j], sides[i])[0];
                    if (d < tempDistHandleSide) {
                        tempDistHandleSide = d;
                    }

                }
                distances[i] = tempDistHandleSide;
                tempDistHandleSide =
                        MAXVALUE;
            }
//  

            return min(distances);
        } else {
            return MAXVALUE; // return an impossible value for a selected grob, (i.e. distance to self)
        }

    }

    // TODO: Using the center of a selected side to get the distance to another side? Determining the right side for rotating.
    // returns the indexes of the shortest side - handle distance for a pair of grobs.
    int[] closestSideHandlePair(Grob grob1, Grob grob2) {
        int[] tuple = new int[2];
        RPoint[] handles2 = grob2.getHandles();
        RShape[] sides2 = grob2.getSides();
        RShape[] sides1 = grob1.getSides();
        float[] distances = new float[sides1.length];
        int[] handleNums = new int[sides1.length];
        float tempDistHandleSide = MAXVALUE;
        int handleNum = -1;
        int tempHandleNum = -1;
        int sideNum = -1;

        // looping through sides of unselected grobs to find closest handle-side relation.

        for (int i = 0; i <
                distances.length; i++) {
            for (int j = 0; j <
                    handles2.length; j++) {
                //for each side, loop through the handles on grob2 and find shortest distance.
                float d1 = dist_ClosestPointOnSegment(handles2[j], sides1[i])[0];
                if (d1 < tempDistHandleSide) {
                    tempDistHandleSide = d1;
                    tempHandleNum =
                            j;
                    float dc = dist_ClosestPointOnSegment(sides2[j].getCenter(), sides1[i])[0];
                    if (dc < tempDistHandleSide) {
                        tempDistHandleSide = dc;
                    }

                }
            }
            distances[i] = tempDistHandleSide;
            handleNums[i] = tempHandleNum; // save a corresponding handle number.
            tempDistHandleSide =
                    MAXVALUE;
        }

//            sideNum = getSmallestValueIndex(distances);
// we get the corresponding handle on grob2 for the side of grob.
//            handleNum = handleNums[sideNum];
// setting the selected side and handle.
//            grob.setSelectedSideNum(sideNum);
//            grob2.setSelectedHandle(handleNum);
//            println("selected handle "+handleNum);
//            println("selected side " + sideNum);

        sideNum = getSmallestValueIndex(distances);
        tuple[0] = sideNum;
        tuple[1] = handleNums[sideNum];

        return tuple;
    }

    int[] smallestHandleToHandle(Grob grob1, Grob grob2) {
        int[] tuple = new int[2];
        RPoint[] handles2 = grob2.getHandles();
        RPoint[] handles1 = grob1.getHandles();
        float[] distances = new float[handles1.length];
        int[] handleNums2 = new int[distances.length];
        float tempDistHandleSide = MAXVALUE;
        int tempHandleNum = -1;
        int handleNum1 = -1;

        // looping through sides of unselected grobs to find closest handle-side relation.

        for (int i = 0; i <
                distances.length; i++) {
            for (int j = 0; j <
                    handles2.length; j++) {
                //for each side, loop through the handles on grob2 and find shortest distance.
                float d1 = distanceToPoint(handles2[j], handles1[i]);
                if (d1 < tempDistHandleSide) {
                    tempDistHandleSide = d1;
                    tempHandleNum = j;
                }
            }
            distances[i] = tempDistHandleSide;
            handleNums2[i] = tempHandleNum; // save a corresponding handle number.
            tempDistHandleSide =
                    MAXVALUE;
        }

//            sideNum = getSmallestValueIndex(distances);
// we get the corresponding handle on grob2 for the side of grob.
//            handleNum = handleNums[sideNum];
// setting the selected side and handle.
//            grob.setSelectedSideNum(sideNum);
//            grob2.setSelectedHandle(handleNum);
//            println("selected handle "+handleNum);
//            println("selected side " + sideNum);

        handleNum1 = getSmallestValueIndex(distances);
        tuple[0] = handleNum1;
        tuple[1] = handleNums2[handleNum1];

        return tuple;
    }

    int getClosestSideToPoint(Grob grob, RPoint point) {
        RShape[] sides = grob.getSides();
        float[] distances = new float[sides.length];
        int sideNum = -1;

        for (int i = 0; i <
                distances.length; i++) {
            distances[i] = dist_ClosestPointOnSegment(point, sides[i])[0];
        }
        return getSmallestValueIndex(distances);
    }

    Grob getClosestGrobToPoint(
            int x, int y) {
        float[] dist = new float[items.size()];
        for (Grob grob_dist : items) {
            grob_dist.update();
            grob_dist.draw();
            // populating the list of dist, determining the closest grob to a point
            dist[items.indexOf(grob_dist)] = (distanceGrobToPoint(grob_dist, x, y)); // distance of grob to point
        }

        Grob grobC = items.get(getClosestGrobIndex(dist));
        return grobC;
    }

    int getClosestGrobIndex(float[] distances) {
        //  get the index of the closest non-selected grob using a list of dist
        float min = max(distances);
        int closest = -1;
        for (int i = 0; i <
                distances.length; i++) {
            if (distances[i] <= min && !items.get(i).isSelected()) {
                min = distances[i];
                closest =
                        i;
            }

        }
        return closest;
        //min value; closest grob;
    }

    Grob getClosestGrob(Grob otherGrob) {

        Grob currentGrob = null;
        float currentDistance = MAX_FLOAT;

        for (Grob grob : items) {
            float d = distanceGrobToGrob(grob, otherGrob);
            if (d < currentDistance) {
                currentGrob = grob;
                currentDistance =
                        d;
            }

        }
        return currentGrob;
    }

    //Transform and translate functions of grobs
    void toTop() {//shifts the grob to top of the draw order
        for (Grob grob : selectedItems) {
            Grob grobShift = grob;
            items.remove(grob);
            items.add(items.size(), grobShift);
            //println(items.indexOf(grobToTop));
        }

    }

    void upOne() {//shifts the grob one step up in the draw order
        for (Grob grob : selectedItems) {
            Grob grobShift = grob;
            int indexnr = items.indexOf(grob);
            if (indexnr < items.size() - 1) {
                items.remove(grob);
                items.add(indexnr + 1, grobShift);
            } else {
                return;
            }

        }
    }

    void downOne() {//shifts the grob one step down in the draw
        for (Grob grob : selectedItems) {
            Grob grobShift = grob;
            int indexnr = items.indexOf(grob);
            if (indexnr > 1) {
                items.remove(grob);
                println(items.indexOf(grob));
                items.add(indexnr - 1, grobShift);
            } else {
                return;
            }

        }
    }

    void toBottom() {//shifts the grob to bottom of the draw order
        for (Grob grob : selectedItems) {
            Grob grobShift = grob;
            items.remove(grob);
            items.add(0, grobShift);
            //println(items.indexOf(grobShift));
        }

    }

    void rotateSelected(int degrees) {
        for (Grob grob : selectedItems) {
            grob.rotateGrob(degrees);
        }

    }

    void setRotationCenter(RPoint p) {
        rotationCenter = p;
    }

    void setRotationCenter(int x, int y) {
        if (rotationCenter == null) {
            rotationCenter = new RPoint(x, y);
        } else {
            rotationCenter.x = x;
            rotationCenter.y = y;
        }
    }

    RPoint getRotationCenter() {
        return rotationCenter;
    }

    private void moveSelected(int dx, int dy) {
        for (Grob grob : selectedItems) {
            grob.setXpos(grob.getXpos() + dx);
            grob.setYpos(grob.getYpos() + dy);
        }

    }

    void intersectSelected(Grob grob) {
    }//intersection of contours…

    void mirrorSelected() {
        for (Grob grob : selectedItems) {
            grob.mirrorGrob();
        }

    }

    void resizeSelected(float scale) {
        for (Grob grob : selectedItems) {
            grob.scale(scale);
        }

    }

    // Recoloring functions
    void recolorH() {
        float h, s, b;

        for (Grob grob : selectedItems) {
            h = hue(grob.getFillColor());
            s =
                    saturation(grob.getFillColor());
            b =
                    brightness(grob.getFillColor());
            h =
                    h + 5;
            grob.setFillColor(h, s, b);
        }

    }

    void recolorS() {
        float h, s, b;
        for (Grob grob : selectedItems) {
            h = hue(grob.getFillColor());
            s =
                    saturation(grob.getFillColor());
            b =
                    brightness(grob.getFillColor());
            s =
                    s + 5;
            grob.setFillColor(h, s, b);
        }

    }

    void recolorB() {
        Random random = new Random();
        int r = random.nextInt(10) + 1;
        float h, s, b;
        for (Grob grob : selectedItems) {
            h = hue(grob.getFillColor());
            s =
                    saturation(grob.getFillColor());
            b =
                    brightness(grob.getFillColor());
            if (b < 100) {
                b += 1;
            } else {
                b = 1;//minor hack to retain H and S values.
            }

            grob.setFillColor(h, s, b);
        }

    }

    // shelf (menu) functions
    ArrayList initiateShelf(String path) { // the "shelf" menu objects
        ArrayList shelfProperties;
        shelfProperties =
                new ArrayList();

        String[] shelflines =
                loadStrings(path);
        fileInput(shelflines);

        for (int i = 0; i <
                shelflines.length; i++) {
            String[] pieces = split(shelflines[i], '\t'); // load data into arraylist
            shelfProperties.add(new GrobProps(pieces));
        }

        for (int i = 0; i <
                shelfProperties.size(); i++) { // creating the Grobs using properties
            GrobProps grobprops = (GrobProps) shelfProperties.get(i);
            Grob shelfGrob = new Grob(this, null, grobprops.object_id, grobprops.xpos,
                    grobprops.ypos, grobprops.size, grobprops.rotation,
                    grobprops.fillColor);
            shelfGrob.setIsDraggable(false);
            shelf.add(shelfGrob);
        }

        return shelf;
    }

    void updateShelf() {
        Selectable aI = null;

        for (int i = 0; i <
                shelf.size(); i++) {
            Grob grob = (Grob) shelf.get(i);
            grob.update();
            grob.draw();
            if (grob.getShape().contains(mouseX, mouseY)) {
                // setting active item to the hovered Grob
                aI = ActiveItem(grob);
            }
        }
        activeItemShelf = aI;

    }

    Grob addGrobFromShelf(Grob shelfGrob) { // add a grob form shelf to items, select & toggle
        if (shelfGrob == null) {
            return null;
        }

        Grob newGrob = shelfGrob.copyGrob(null, shelfGrob.getXpos(), shelfGrob.getYpos(), shelfGrob.getXpos(), shelfGrob.getYpos());
        singleSelect(newGrob);
        items.add(newGrob);
        //initializing shape:
        newGrob.update();
        newGrob.setupTransform();
        return newGrob;
    }

    // read and write functions
    ArrayList fileInput(String[] lines) {
        for (int i = 0; i <
                lines.length; i++) {
            String[] pieces = split(lines[i], '\t'); // load data into arraylist
            properties.add(new GrobProps(pieces));
            itemCount++;

        }


        return properties;
    }

    ArrayList addGrobs(ArrayList properties) {
        for (int i = 0; i <
                itemCount; i++) { // creating the Grobs using properties
            GrobProps grobprops = (GrobProps) properties.get(i);
            items.add(new Grob(this, null, grobprops.object_id, grobprops.xpos,
                    grobprops.ypos, grobprops.size, grobprops.rotation,
                    grobprops.fillColor));
        }

        return items;
    }

    void reset() { // empties the current canvas & grob properties
        items.clear();
        properties.clear();
        itemCount =
                0;
    }

    void fileOutput() {// not adapted to the new additions to the grobs.

        String[] outputLines = new String[items.size()];
        String filenumber;

        for (int i = 0; i <
                items.size(); i++) {
            Grob grob = (Grob) items.get(i);
//            if (grob.isGroup()){
//                Group group= (Group) grob;
//                for (Grob child:  group.getChildren()){
//                outputLines[i]}
            outputLines[i] = grob.object_type + "\t" + (grob.getXpos() / gridSize) + "\t" + (grob.getYpos() / gridSize) + "\t" + grob.getSize() + "\t" + grob.rotation + "\t" + hex(grob.getFillColor());
        }

//        if (count < 10) {
//            filenumber = "0" + str(count);
//        } else {
//            filenumber = str(count);
//        }
        JFileChooser saver = new JFileChooser("scenes");
        saver.showSaveDialog(null);
        // saver.setName("canvas" + n + ".txt");

        File f = saver.getSelectedFile();

        if (f != null) {
            saveStrings(f, outputLines);
        }
//saveStrings("scenes/canvas" + filenumber + ".txt", lines);

        background(250);
        //println("output");
    }

    @Override
    public void setup() {
        RG.init(this);
        colorMode(HSB, 360, 100, 100, 100);
        frameRate(45);
        marquee =
                new Marquee(this);
        selectedItems =
                new HashSet();
        temporarySelection =
                new HashSet();
        nonSelectedItems =
                new HashSet();

        menu =
                new Menu(this, 10, 10, 100, 15);
        unibody =
                loadFont("Unibody8-Regular-8.vlw");


        textFont(unibody);


        size(canvasWidth, canvasHeight);
        smooth();

        background(0);
        properties =
                new ArrayList(itemCount);
        items =
                new ArrayList(itemCount);

        shelf =
                new ArrayList();

        textbox =
                new TextBox(this, "startscene", 60, 65, 100, 8);

        snapButton = new Button(this, "snap", 10, 65, 30, 10);


        lines =
                loadStrings("scenes/startscene_empty.txt");
        fileInput(lines);
        addGrobs(properties);
        println(items);

        initiateShelf("scenes/shelf-menu.txt"); // loading a "shelf" menu of Grobs


    }

    @Override
    public void draw() {
        background(0);


        //Grob interface

        stroke(defaultColor);
        fill(defaultColor);

        strokeWeight(1);
        line(0, 0 + canvasHeight / 10, canvasWidth, 0 + canvasHeight / 10);
        snapButton.draw();
        if (snapButton.getIsPressed()) {
            snapIsOn = true;
        } else {
            snapIsOn = false;
        }
//        if (rotationCenter!=null){
//            ellipse(rotationCenter.x, rotationCenter.y ,10 ,10);
//        }
        noStroke();


        textbox.draw();

        noStroke();
        updateShelf();// updating the shelf grobs
        updateItems();
        if (activeItemItems != null && activeItemShelf == null) {
            active = activeItemItems;
        } else if (activeItemItems == null && activeItemShelf != null) {
            active = activeItemShelf;
        } else {
            active = null;
        }

        if (snapButton.getShape().contains(mouseX, mouseY)) {
            active = snapButton;
        }

        if (mousePressed) {//draw the marquee selection over the boxes
            marquee.draw();


        }

        if (snaptargetGrob != null) {
//            ellipse(cornerPointSnappingGrobX, cornerPointSnappingGrobY, 10, 10);
        }

    }

    @Override
    public void keyPressed(KeyEvent e) {
        switch (e.getKeyChar()) {
            case 'g':
                doGroup();
                break;

            case 'u':
                doUngroup();
                break;

            case 'o':
                doOpen();
                break;

            case 'h':
                textbox.update("hue");
                recolorH();

                break;

            case 'b':
                textbox.update("brightness");
                recolorB();

                break;

            case 'q':
                rotateContinuosly = !rotateContinuosly;
                break;

            case BACKSPACE:
                deleteGrobs();
                break;

            case 's': // saves the canvas TODO: update save function for colors etc
                doSave();
                break;

            case 'R':
                textbox.update("rotate ccw");
                rotateSelected(-15);
                break;

            case 'm':
                textbox.update("mirror");
                mirrorSelected();

                break;

            case 'r':
                textbox.update("rotate cw");
                rotateSelected(15);
                break;

            case '+':
                resizeSelected((float) 0.1);
                break;

            case '-':
                resizeSelected((float) -0.1);
                break;

        }


        shiftPressed = (e.getModifiers() & InputEvent.SHIFT_MASK) > 0;
        altPressed =
                (e.getModifiers() & InputEvent.ALT_MASK) > 0;

        switch (e.getKeyCode()) {
            case UP:
                if (shiftPressed) {
                    if (altPressed) {
                        toTop();
                    } else {
                        upOne();
                    }

                    println("up");
                } else {
                    moveSelected(0, -gridSize);
                }

                break;
            case DOWN:
                if (shiftPressed) {
                    if (altPressed) {
                        toBottom();
                    } else {
                        downOne();
                    }

                    println("down");
                } else {
                    moveSelected(0, gridSize);
                }

                break;
            case LEFT:
                moveSelected(-gridSize, 0);
                break;

            case RIGHT:
                moveSelected(gridSize, 0);
                break;

        }


    }

    @Override
    public void keyReleased(KeyEvent arg0) {
        super.keyReleased(arg0);
        shiftPressed =
                false;
        altPressed =
                false;
    }

    private void doSave() {
        textbox.update("save");

        for (Grob grob : items) {
            if (grob.isGroup()) {
                select(grob);
            }

        }

        for (Grob grob : selectedItems) { // ungroup all items before saving
            if (grob.isGroup()) {
                unGroup((Group) grob);
            }

        }
        fileOutput();
    }

    private void doUngroup() {
        textbox.update("ungroup");

        for (Grob grob : selectedItems) {
            if (grob.isGroup()) {
                unGroup((Group) grob);
            }

        }
    }

    private void doGroup() {
        textbox.update("new group");
        if (selectionSize() == 0) {
            return;
        }

        createGroup();
    }

    private void doOpen() {
        textbox.update("open");


        JFileChooser chooser = new JFileChooser("scenes");
        chooser.showOpenDialog(null);
        File f = chooser.getSelectedFile();
        if (f != null) {
            lines =
                    loadStrings(f);
            reset();

            fileInput(lines);
            addGrobs(properties);
            chooser.cancelSelection();


        }

        chooser.cancelSelection();
    }

    @Override
    public void mousePressed() {
        mousePressed = true;

        boolean isOver = false;
        Grob shelfGrob = null;

        if (mouseButton == 37 || mouseButton == 39) {

            if (isOver(snapButton)) {
                snapButton.toggle();
            }

            // method for copying grobs from the shelf to Items.

            if (mouseY < canvasHeight / 10) { // Check whether the mouse is over the shelf.

                for (int i = 0; i <
                        shelf.size(); i++) {
                    Grob Sgrob = (Grob) shelf.get(i);


                    Sgrob.grobSelect(); // controls which Grobs are selected

                    isOver |=
                            isOver(Sgrob); // is equal to self, or grob.isOver true | true = true; true | false = true…
                    if (Sgrob.isSelected()) {
                        shelfGrob = Sgrob;
                        deselect(Sgrob);
                    }

                }
            }

            if (shelfGrob != null) {
                dragGrob = addGrobFromShelf(shelfGrob);


            } else {
                for (int i = 0; i <
                        items.size(); i++) {
                    Grob grob = (Grob) items.get(i);

                    grob.grobSelect(); // controls which Grobs are selected
                    isOver |=
                            isOver(grob); // is equal to self, or grob.isOver true | true = true; true | false = true…
                }

            }
        }

        previousX = mouseX;
        previousY =
                mouseY;
        if (dragGrob == null) {
            for (Grob grob : items) {
                if (isOver(grob)) {
                    dragGrobSelectedSideNr = grob.selectSide(6);
                    dragGrob =
                            grob;


                }

            }
        }


        if (!isOver) {
            if (mouseButton == 39) {//secondary mousebutton
                Grob clonedGrob = null;
                for (int i = 0; i <
                        items.size(); i++) {
                    Grob grob = (Grob) items.get(i);
                    if (grob.isSelected()) {
                        textbox.update("copy!");
                        clonedGrob =
                                grob.copyGrob(null, mouseX, mouseY, min_x, min_y);
                        deselect(grob);
                        items.add(clonedGrob);
                        selectTemporary(clonedGrob);
                    }

                }

                if (temporarySelection.size() != 0) {
                    for (Grob grob : items) {
                        if (grob.isTemporarySelected()) {
                            select(grob);
                            if (grob.getXpos() == mouseX && grob.getYpos() == mouseY) {
                                dragGrob = grob;
                            }

                        }
                    }

                    deselectAllTemporary();
                }

            } else {
                deselectAll();
            }

            if (dragGrob == null) {
                marquee.press(mouseX, mouseY);
            }

        }
    }

    @Override
    public void mouseReleased() {
        marquee.release();
        mousePressed = false;


        dragGrob =
                null;
        snaptargetGrob =null;
        snapSidetargetGrob=null;
        dragGrobSelectedSideNr = -1;


        if (temporarySelection.size() != 0) {
            for (Grob grob : items) {
                if (grob.isTemporarySelected()) {
                    select(grob);
                }

            }
            deselectAllTemporary();
        }

    }

    @Override
    public void mouseDragged() {

        marquee.drag();
        if (dragGrob == null) {
            return;
        }

        int dx = mouseX - previousX;
        int dy = mouseY - previousY;
        if (dx == 0 && dy == 0) {
            return;
        }

        previousX = mouseX;
        previousY =
                mouseY;


        float[] distCornerCorner;

//            distCornerCorner =new float[4];

        distCornerCorner =
                new float[2];

        RPoint dragGrobHandle;

        RShape dragGrobSide;

        RPoint handleC0;//  handles of selected side on snaptargetGrob.

        RPoint handleC1;

        RPoint closestGrobHandle;

        RShape closestGrobSide;

        Grob closestGrob;


        closestGrob = getClosestGrob(dragGrob);
        dragGrobHandle = dragGrob.getHandle(dragGrobHandleNr);//    selected handle on grob
        closestGrobSide = closestGrob.getSide(closestGrobSideNr);


        float d = distanceToPoint(virtualSnapPointX, virtualSnapPointY, cornerPointSnappingGrobX, cornerPointSnappingGrobY);


        // Check if we're currently snapped to another grob.
        if (snaptargetGrob != null && dragGrob != null) {

            if (snapSidetargetGrob!=null){
             // if we have snapped to a side, but not a corner, the grob can be dragged along the side.
            // we find the closest point on snappingGrob and translate the corner of draggingGrob there.
            float handleSnapDistance=SNAP_DISTANCE-8;
            RPoint snapHandle = closestPointOnSegment(dragGrobHandle, closestGrobSide);
            int sX = Math.round(snapHandle.x);
            int sY = Math.round(snapHandle.y);

            cornerPointDraggingGrobX =
                    Math.round(dragGrobHandle.x);
            cornerPointDraggingGrobY =
                    Math.round(dragGrobHandle.y);

            int[] handlePair = smallestHandleToHandle(snaptargetGrob, dragGrob);
            RPoint handleS = snaptargetGrob.getHandle(handlePair[0]);
            RPoint handleD = dragGrob.getHandle(handlePair[1]);

            ellipse(handleS.x, handleS.y, 5, 5);
            ellipse(handleD.x, handleD.y, 5, 5);
            // moving draggingGrob
            if (distanceToPoint(handleS, handleD) > handleSnapDistance) {
                moveSelected((sX - cornerPointDraggingGrobX) + dx, (sY - cornerPointDraggingGrobY) + dy);
            } else {
                moveSelected(Math.round(handleS.x - handleD.x), Math.round(handleS.y - handleD.y));
                snapSidetargetGrob=snaptargetGrob;
           }
                 d = distanceToPoint(virtualSnapPointX, virtualSnapPointY, sX, sY);
            } 

            // Change the value of the dragging point.
            // This acts as a virtual point that gets compared to the point
            // you snapped to to see if it's still within distance.
            // The virtual snap point can take any value, while sX, sY is always on the side of snappingGrob.

            virtualSnapPointX += dx;
            virtualSnapPointY += dy;
//            }


            // We're still inside of snapping distance.
            if (d <= SNAP_DISTANCE) {
                // Do nothing.
                return;
            } else {
                println("deSnap");
                // If we're outside of the snapping distance, we're no longer snapping.
                snaptargetGrob =
                        null;
                // Move away, back to our non-snapped relativePosition.
                // This sets the point to the virtual point we've been dragging while snapped.
                // This makes sure we don't immediately snap again.
//                rotateSelected(-snapRotation);
                setRotationCenter(null);
                moveSelected(virtualSnapPointX - cornerPointDraggingGrobX, virtualSnapPointY - cornerPointDraggingGrobY);
                return;
            }
        }
        // The normal move method
        moveSelected(dx, dy);

        // snapping methods
        if (items.size() >= 2 && dragGrob.isIsDraggable() && snapIsOn) {


            // first point of selected line

            distanceGrobToGrob =
                    distanceGrobToGrob(closestGrob, dragGrob);
//            println("distance" + distanceGrobToGrob);


            // See if the closest grob is within snapping distance.
            if (distanceGrobToGrob <= SNAP_DISTANCE) {
//                dragGrobSide = dragGrob.getSide(dragGrobSelectedSideNr);


                int[] cGsideDGHandle = closestSideHandlePair(closestGrob, dragGrob); // needed if a side isn't preselected
                closestGrobSideNr = cGsideDGHandle[0];// unused if we have a pre-selected side
                dragGrobHandleNr = cGsideDGHandle[1];

                dragGrobHandle =
                        dragGrob.getHandle(dragGrobHandleNr);
                handleC0 =
                        closestGrob.getHandle(closestGrobSideNr);
                handleC1 =
                        closestGrob.getHandle(closestGrobSideNr + 1);

                //----- alternate version that doesn't require using a pre-selected side on dragGrob:
                distCornerCorner[0] = distanceToPoint(handleC0, dragGrobHandle);// 0->h
                distCornerCorner[1] = distanceToPoint(handleC1, dragGrobHandle);// 1->h

                int cornerPair = getSmallestValueIndex(distCornerCorner);

                if (cornerPair == 0) {
                    closestGrobHandle = handleC0;
                } else {
                    closestGrobHandle = handleC1;

                }
// -----


// original version with pre-selected side.
//                    distCornerCorner[0] = distanceToPoint(closestGrob.getSelectedSideHandle(0), dragGrob.getSelectedSideHandle(0));// 0->0
//                    distCornerCorner[1] = distanceToPoint(closestGrob.getSelectedSideHandle(0), dragGrob.getSelectedSideHandle(1));// 0->1
//                    distCornerCorner[2] = distanceToPoint(closestGrob.getSelectedSideHandle(1), dragGrob.getSelectedSideHandle(0));// 1->0
//                    distCornerCorner[3] = distanceToPoint(closestGrob.getSelectedSideHandle(1), dragGrob.getSelectedSideHandle(1));// 1->1
//
//                    // base case, first handle of each side
//                    handle =
//                            dragGrob.getSelectedSideHandle(0);
//                    handleC =
//                            closestGrob.getSelectedSideHandle(0);
//
//                    // lookup for determining the handles
//
//                    if (cornerPair == 0) {
//                        handleC = closestGrob.getSelectedSideHandle(0);
//                        handle =
//                                dragGrob.getSelectedSideHandle(0);
//
//                    } else if (cornerPair == 1) {
//                        handleC = closestGrob.getSelectedSideHandle(0);
//                        handle =
//                                dragGrob.getSelectedSideHandle(1);
//
//                    } else if (cornerPair == 2) {
//                        handleC = closestGrob.getSelectedSideHandle(1);
//                        handle =
//                                dragGrob.getSelectedSideHandle(0);
//                    } else if (cornerPair == 3) {
//                        handleC = closestGrob.getSelectedSideHandle(1);
//                        handle =
//                                dragGrob.getSelectedSideHandle(1);
//                    }
                float[] distanceAndSnapPoint = (dist_ClosestPointOnSegment(dragGrobHandle, closestGrobSide)); // 0 is distance, 1,2 is x, y of point on side
                distanceHandleToSide = distanceAndSnapPoint[0];
                snapPointX = Math.round(distanceAndSnapPoint[1]);
                snapPointY = Math.round(distanceAndSnapPoint[2]);

                dragGrobHandle =
                        dragGrob.getHandle(dragGrobHandleNr);

                if (distanceHandleToSide > SNAP_DISTANCE) {
                    // do nothing.
                } else {


                    // Let's snap!
                    textbox.update("snap");
//                    snapRotation =            calculateRotationSides(closestGrobSide, dragGrobSide);
//                    println("snapRotation " + snapRotation);
//                    rotateSelected(snapRotation);

//                    // we need to update dragGrob's relativePosition after rotation to get the correct corner point,
//                    // since otherwise we are translating from the non-rotated corner point.
//                    // This should not be necessary if rotating from corners.
//                    dragGrob.update();
//                    dragGrob.setupTransform();

//                    dragGrobHandle =
//                            dragGrob.getHandle(dragGrobHandleNr);

                    cornerPointDraggingGrobX =
                            Math.round(dragGrobHandle.x);
                    cornerPointDraggingGrobY =
                            Math.round(dragGrobHandle.y);

                    cornerPointSnappingGrobX =
                            Math.round(closestGrobHandle.x);
                    cornerPointSnappingGrobY =
                            Math.round(closestGrobHandle.y);

                    virtualSnapPointX = cornerPointDraggingGrobX;
                    virtualSnapPointY = cornerPointDraggingGrobY;

                    int snapValueX = 0;
                    int snapValueY = 0;
                    float distanceHandleToHandle = distanceToPoint(dragGrobHandle, closestGrobHandle);

                    if (distanceHandleToHandle <= SNAP_DISTANCE) {
                        snapValueX = cornerPointSnappingGrobX - cornerPointDraggingGrobX-dx;
                        snapValueY = cornerPointSnappingGrobY - cornerPointDraggingGrobY-dy;
                        snaptargetGrob =
                                closestGrob;
                    } else {
                        snapValueX = (snapPointX - cornerPointDraggingGrobX);
                        snapValueY = (snapPointY - cornerPointDraggingGrobY);
//                      println("grobC side " + side);
                        snapSidetargetGrob = closestGrob;   
                        snaptargetGrob = closestGrob;

//                    rotateSelectedCornerpoint(snapRotation, closestGrobHandle);
//                    // if rotating from a corner point, the order of events is not strict.
//
//                    // likewise, setting the cornerPoint to match it's relativePosition after snapping. This should not be necessary if rotating from corners.
//                    dragGrob.update();
//                    dragGrob.setupTransform();

//                    dragGrobHandle =
//                            dragGrob.getHandle(dragGrobHandleNr);
//                    cornerPointDraggingGrobX =
//                            Math.round(dragGrobHandle.x);
//                    cornerPointDraggingGrobY =
//                            Math.round(dragGrobHandle.y);


                    }
                    moveSelected(snapValueX, snapValueY);
//                    setRotationCenter(snapPointX, snapPointY);

                    println("distance " + distanceHandleToSide);
                    println("SNAP: draggingGrobC x" + cornerPointDraggingGrobX + ", y" + cornerPointDraggingGrobY + " snapGrobC x" + cornerPointSnappingGrobX + ", y" + cornerPointSnappingGrobY);
                }

            }
//            println("draggingGrobC x" + cornerPointDraggingGrobX + ", y" + cornerPointDraggingGrobY + " snapGrobC x" + cornerPointSnappingGrobX + ", y" + cornerPointSnappingGrobY);
        }

    }

    class GrobProps { // this class is the possible properties for the Grobs

        int object_id;
        int xpos;
        int ypos;
        int size;
        int rotation = 0;
        int fillColor = 255;
        int overColor = 150;
        int pressedColor = 0;
        boolean over = false; // true when mouse is over object
        boolean pressed = false;

        GrobProps(String[] pieces) {
            assert (pieces.length == 6);
            object_id = Integer.parseInt(pieces[0]);
            xpos = Integer.parseInt(pieces[1]) * gridSize;
            ypos = Integer.parseInt(pieces[2]) * gridSize;
            size = Integer.parseInt(pieces[3]);
            rotation = Integer.parseInt(pieces[4]);


//            // parsing a color from a hex value:
            String st = (pieces[5]);
            String cstr = st.substring(4);
            fillColor = Integer.parseInt(cstr, 16) | 0xFF000000;
            //  fillColor = Integer.parseInt(pieces[5]);
        }
    }
}//the mandatory end bracket


